﻿using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;

using System;
using System.Diagnostics;
using System.Linq;

using zijian666.Core;
using zijian666.WebApi.Attributes;

namespace zijian666.WebApi.Middleware
{
    /// <summary>
    /// 接口过滤器: 格式化返回值
    /// </summary>
    class FormatResultFilter : IActionFilter
    {
        private readonly IResultFormatter _builder;

        public FormatResultFilter(IResultFormatter builder) => _builder = builder ?? throw new ArgumentNullException(nameof(builder));

        private static IActionResult Result(object result) => (result as IActionResult) ?? new JsonResult(result);

        public void OnActionExecuting(ActionExecutingContext context)
        {

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context is null)
            {
                return;
            }
            if (context.HttpContext?.Response?.Headers is IHeaderDictionary headers)
            {
                headers["X-Correlation-ID"] = context.HttpContext.TraceIdentifier;
                if (context.HttpContext.Items["Stopwatch"] is Stopwatch sw)
                {
                    sw.Stop();
                    headers["X-Timing"] = sw.ElapsedMilliseconds + "";
                }
            }
            if (context.ActionDescriptor?.FilterDescriptors?.Any(x => x.Filter is DontFormatResultAttribute) ?? true)
            {
                return;
            }
            try
            {
                if (context.Exception is not null)
                {
                    var ex = _builder.Error(context.Exception);
                    if (ex is Exception e)
                    {
                        ErrorReporter.Report(e, this, context);
                        context.Exception = e;
                    }
                    else
                    {
                        context.Result = Result(ex);
                        context.Exception = null;
                    }
                    return;
                }
                switch (context.Result)
                {
                    case JsonResult result:
                        var json = _builder.Ok(result.Value);
                        if (!ReferenceEquals(json, result.Value))
                        {
                            context.Result = Result(json);
                        }
                        return;
                    case ObjectResult result when result.Value is Exception ex:
                        var fail = _builder.Fail(ex.HResult, ex.Message);
                        if (!ReferenceEquals(fail, result.Value))
                        {
                            context.Result = Result(fail);
                        }
                        return;
                    case ObjectResult result when result.StatusCode is null or 200:
                        var ok = _builder.Ok(result.Value);
                        if (!ReferenceEquals(ok, result.Value))
                        {
                            context.Result = Result(ok);
                        }
                        return;
                    case ObjectResult result:
                        var code = (result as IStatusCodeActionResult)?.StatusCode ?? 0;
                        var data = result.Value;
                        var subcode = result.GetType().Name;
                        if (subcode.EndsWith("ObjectResult", StringComparison.OrdinalIgnoreCase))
                        {
                            subcode = subcode[0..^"ObjectResult".Length];
                        }
                        else if (subcode.EndsWith("Result", StringComparison.OrdinalIgnoreCase))
                        {
                            subcode = subcode[0..^"Result".Length];
                        }
                        if (subcode.Length == 0)
                        {
                            subcode = "ok";
                        }
                        context.Result = new JsonResult(_builder.Custom(code, subcode, data, subcode));
                        return;
                    case OkResult:
                        context.Result = new JsonResult(_builder.Ok(null));
                        return;
                    case null:
                    case EmptyResult:
                        context.Result = new JsonResult(_builder.Empty());
                        return;
                    default:
                        return;
                }
            }
            catch (Exception e)
            {
                ErrorReporter.Report(e, this, context);
                if (context.Exception is not null)
                {
                    context.Exception = new AggregateException(e, context.Exception);
                }
                else
                {
                    context.Exception = e;
                }
            }
        }
    }
}
